﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            this.linkLabel1.MouseMove += LinkLabel1_MouseMove;
            this.linkLabel1.LostFocus += LinkLabel1_MouseLeave;
            this.linkLabel1.MouseEnter += LinkLabel1_MouseEnter;

            //this.toolTip1.ShowAlways = true;
            //this.toolTip1 = new  ToolTip();

            var timer = new Timer { Interval = 100 };
            timer.Tick += delegate
            {
                this.Text = DateTime.Now.ToString();
            };
            timer.Start();

            //this.AddFocusHandlers(this.panel1);

            this.linkLabel1.LinkClicked += (s, _) => Console.WriteLine("LinkClicked");
            this.linkLabel1.MouseClick += (s, _) => Console.WriteLine("MouseClick");
            this.linkLabel1.Click += (s, _) => Console.WriteLine("Click");
            
        }

        private  void AddFocusHandlers(Control control)
        {
            control.GotFocus += Control_GotFocus;
            control.LostFocus += Control_LostFocus;

            foreach (Control child in control.Controls)
            {
                this.AddFocusHandlers(child);
            }

            _ = this;
        }

        private  void Control_LostFocus(object sender, EventArgs e)
        {
            Console.WriteLine("失去焦点: " + sender);
            this.panel1.BackColor = Color.Silver;
        }

        private  void Control_GotFocus(object sender, EventArgs e)
        {
            Console.WriteLine("获取焦点: " + sender);
            this.panel1.BackColor = Color.Red;
        }


        private void LinkLabel1_MouseEnter(object sender, EventArgs e)
        {
            //ShowToolTip(this.linkLabel1, new Point(0, this.linkLabel1.Height), "测试");
            this.toolTip1.Show("测试", this.linkLabel1, 0, this.linkLabel1.Height);
            //this.toolTip1.ShowHorizontal(this.linkLabel1, "测试", this.linkLabel1.Height,0,  -this.linkLabel1.Width);
        }

        private void LinkLabel1_MouseLeave(object sender, EventArgs e)
        {
            this.toolTip1.Hide(this.linkLabel1);
        }

        private void LinkLabel1_MouseMove(object sender, MouseEventArgs e)
        {

        }

        private void tmr_Test(object state)
        {

        }

        private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            /*this.Cursor = Cursors.WaitCursor;
            for (int i = 0; i < 10; i++)
            {
                this.Text = DateTime.Now.ToString();
                System.Threading.Thread.Sleep(300);
            }
            this.Cursor = Cursors.Default;

            var form = new Form();
            form.Show();*/
        }

        private static readonly MethodInfo SetTrackPosition = typeof(ToolTip).GetMethod("SetTrackPosition", BindingFlags.Instance | BindingFlags.NonPublic);
        private static readonly MethodInfo SetTool = typeof(ToolTip).GetMethod("SetTool", BindingFlags.Instance | BindingFlags.NonPublic);
        private void ShowToolTip(Control control, Point point, string text)
        {
            SetTrackPosition.Invoke(this.toolTip1, new object[] { point.X, point.Y });
            SetTool.Invoke(this.toolTip1, new object[] { control, text, 2, point });
        }
    }

    public class BaseToolTipEx : ToolTip
    {
        protected BaseToolTipEx()
        {
            this.UseAnimation = false;
            this.UseFading = false;
            this.OwnerDraw = true;
            this.Popup += this.ToolTipEx_Popup;
            this.Draw += this.ToolTipEx_Draw;
        }

        protected const int LeftPadding = 10; //绘制文本的左侧水平偏移量
        protected const int TopPadding = 10; //绘制文本的顶部垂直偏移量
        protected static readonly Point DrawTextPoint = new Point(LeftPadding, TopPadding); //绘制文本的起始位置
        private static readonly MethodInfo SetTrackPosition = typeof(ToolTip).GetMethod("SetTrackPosition", BindingFlags.Instance | BindingFlags.NonPublic);
        private static readonly MethodInfo SetTool = typeof(ToolTip).GetMethod("SetTool", BindingFlags.Instance | BindingFlags.NonPublic);
        protected Size size; //工具提示实际大小
        private Color foreColor = Color.Black;
        private Color backColor = Color.FromArgb(245, 247, 254);
        private Color borderColor = Color.FromArgb(155, 167, 183);

        /// <summary>
        /// 获取或设置工具提示的前景色
        /// </summary>
        public new Color ForeColor
        {
            get => this.foreColor;
            set
            {
                if (!value.IsEmpty)
                    this.foreColor = value;
            }
        }

        /// <summary>
        /// 获取或设置工具提示的背景色
        /// </summary>
        public new Color BackColor
        {
            get => this.backColor;
            set
            {
                if (!value.IsEmpty)
                    this.backColor = value;
            }
        }

        /// <summary>
        /// 获取或设置边框颜色
        /// </summary>
        public Color BorderColor
        {
            get => this.borderColor;
            set
            {
                if (!value.IsEmpty)
                    this.borderColor = value;
            }
        }

        /// <summary>
        /// 获取或设置字体
        /// </summary>
        public Font Font { get; set; } = new Font("微软雅黑", 9F);

        /// <summary>
        /// 获取一个值，该值指示是否可见
        /// </summary>
        public bool Visible => this.Active;

        private void ToolTipEx_Popup(object sender, PopupEventArgs e) => e.ToolTipSize = this.size;

        private void ToolTipEx_Draw(object sender, DrawToolTipEventArgs e)
        {
            using (var brush = new SolidBrush(this.BackColor))
                e.Graphics.FillRectangle(brush, e.Bounds);

            //绘制边框
            using (var pen = new Pen(this.BorderColor, 1))
            {
                e.Graphics.DrawLines(pen, new[] { new Point(0, e.Bounds.Height - 1), new Point(0, 0), new Point(e.Bounds.Width - 1, 0) });
                e.Graphics.DrawLines(pen, new[] { new Point(0, e.Bounds.Height - 1), new Point(e.Bounds.Width - 1, e.Bounds.Height - 1), new Point(e.Bounds.Width - 1, 0) });
            }

            //绘制文本
            this.DrawText(e);
        }

        /// <summary>
        /// 绘制文本
        /// </summary>
        /// <param name="e">绘制参数</param>
        protected virtual void DrawText(DrawToolTipEventArgs e) => TextRenderer.DrawText(e.Graphics, e.ToolTipText, this.Font, e.Bounds, this.ForeColor);

        /// <summary>
        /// 显示工具提示
        /// </summary>
        /// <param name="f_Control">关联的控件</param>
        /// <param name="f_Text">文本</param>
        /// <param name="f_Point">屏幕坐标</param>
        protected void ShowEx(Control f_Control, string f_Text, Point f_Point)
        {
            SetTrackPosition.Invoke(this, new object[] { f_Point.X, f_Point.Y });
            SetTool.Invoke(this, new object[] { f_Control, f_Text, 2, f_Point });
        }
    }

    /// <summary>
    /// <see cref="ToolTip"/> 扩展
    /// </summary>
    public class ToolTipEx : BaseToolTipEx
    {
        private const int ImageWidth = 20; //图片宽度，目前采用 16*16 尺寸
        private const int Padding = 20; //左侧或底部与屏幕边缘的间隔
        private Image image; //要绘制的图片，目前只支持 16*16 的图片

        protected override void DrawText(DrawToolTipEventArgs e)
        {
            //绘制图片与文本
            if (this.image != null)
            {
                e.Graphics.DrawImage(this.image, LeftPadding + 2, TopPadding); //绘制图片，字体的高度刚好为 16 10 + this.toolTipFont.Height - 16

                int x = LeftPadding; //x 坐标
                int y = TopPadding; //y 坐标
                string[] tmpLines = Regex.Split(e.ToolTipText, @"\r\n|\r|\n");
                for (int i = 0; i < tmpLines.Length; i++)
                {
                    TextRenderer.DrawText(e.Graphics, tmpLines[i], this.Font, new Point(i == 0 ? x + ImageWidth : x, y), this.ForeColor);
                    y += this.Font.Height + 1; //这里不能随便增加间隔高度，因为计算后的总高度是固定的，会导致显示越界问题，+1 刚好
                }
            }
            else
            {
                TextRenderer.DrawText(e.Graphics, e.ToolTipText, this.Font, DrawTextPoint, this.ForeColor);
            }
        }

        /// <summary>
        /// 在水平方向自适应显示
        /// </summary>
        /// <param name="f_Control">关联的控件</param>
        /// <param name="f_Text">要显示的文本</param>
        /// <param name="f_Y">相对于 <paramref name="f_Control"/> 的纵坐标</param>
        /// <param name="f_LeftOffset">相对于 <paramref name="f_Control"/> 的左侧偏移量</param>
        /// <param name="f_RightOffset">相对于 <paramref name="f_Control"/> 的右侧偏移量</param>
        public void ShowHorizontal(Control f_Control, string f_Text, int f_Y, int f_LeftOffset, int f_RightOffset)
        {
            string tmpText;
            Point tmpPoint;
            Size tmpSize;

            //若左右两侧宽度都够，则优先显示在右侧；否则分别计算左右两侧的宽度，显示在较宽的一侧
            Rectangle tmpWorkingArea = Screen.GetWorkingArea(f_Control); //获取工作区的界限
            Point tmpControlLocation = f_Control.PointToScreen(f_Control.Location); //获取关联控件在屏幕上的坐标
            int tmpLeftWidth = tmpControlLocation.X - (tmpWorkingArea.Left + Padding);
            int tmpRightWidth = tmpWorkingArea.Right - (tmpControlLocation.X + f_Control.Width + Padding);

            Size tmpTextSize = TextRenderer.MeasureText(f_Text, this.Font);
            int tmpAdditionalWidth = LeftPadding * 2; //额外宽度
            int tmpActualWidth = tmpTextSize.Width + tmpAdditionalWidth; //实际需要的宽度 = 文本宽度 + 额外宽度
            if (tmpActualWidth + f_LeftOffset <= tmpLeftWidth && tmpActualWidth + f_RightOffset <= tmpRightWidth)
            {
                //说明左右两侧宽度都足够宽，无须换行，此时显示在右侧即可
                tmpText = f_Text;
                tmpPoint = new Point(f_Control.Width + f_RightOffset, f_Y);
                tmpSize = new Size(tmpActualWidth, tmpTextSize.Height + TopPadding * 2);
            }
            else
            {
                int tmpTextMaxWidth = Math.Max(tmpLeftWidth - f_LeftOffset, tmpRightWidth - f_RightOffset) - tmpAdditionalWidth; //文本最大宽度 = 较宽一侧宽度 - 额外宽度 - 偏移量
                tmpText = f_Text;
                Size tmpFormattedTextSize = TextRenderer.MeasureText(tmpText, this.Font); //计算格式化后的文本尺寸
                if (tmpFormattedTextSize.Width > tmpTextMaxWidth)
                    tmpFormattedTextSize = new Size(tmpTextMaxWidth, tmpFormattedTextSize.Height);
                tmpPoint = new Point(tmpLeftWidth > tmpRightWidth ? -(tmpFormattedTextSize.Width + tmpAdditionalWidth + f_LeftOffset) : f_Control.Width + f_RightOffset, f_Y);
                tmpSize = new Size(tmpFormattedTextSize.Width + tmpAdditionalWidth, tmpFormattedTextSize.Height + TopPadding * 2);
            }

            //将当前在控件中的坐标换算成在屏幕中的坐标
            if (f_Control.Parent != null)
                tmpPoint.Offset(f_Control.Parent.PointToScreen(f_Control.Location));
            else
                tmpPoint.Offset(f_Control.Location);

            this.ShowCore(f_Control, tmpText, tmpPoint, tmpSize);
        }

        private void ShowCore(Control f_Control, string f_Text, Point f_Point, Size f_Size)
        {
            this.size = f_Size;
            this.ToolTipTitle = null;
            this.Active = false; //先关闭上次弹出的工具提示
            this.Active = true;

            this.ShowEx(f_Control, f_Text, f_Point);
        }
    }
}
